package com.fun.fun.httpclient;

import com.fun.frame.SourceCode;
import com.fun.profile.HttpClientConstant;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.MessageConstraints;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;

/**
 * 连接池管理类
 */
public class ClientManage extends SourceCode {


	/**
	 * 请求重试管理器
	 */
	private static HttpRequestRetryHandler httpRequestRetryHandler = getHttpRequestRetryHandler();
	/**
	 * 连接池
	 */
	private static PoolingHttpClientConnectionManager connManager = getPool();
	/**
	 * httpclient对象
	 */
	public static CloseableHttpClient httpsClient = getCloseableHttpsClients();

	/**
	 * cookies持久化管理用
	 */
	public static CookieStore cookieStore = new BasicCookieStore();

	/**
	 * 获取连接池
	 *
	 * @return
	 */
	private static PoolingHttpClientConnectionManager getPool() {
		PoolingHttpClientConnectionManager connManager = null;
		// 采用绕过验证的方式处理https请求
		SSLContext sslcontext = createIgnoreVerifySSL();
		// 设置协议http和https对应的处理socket链接工厂的对象
		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.INSTANCE).register("https", new SSLConnectionSocketFactory(sslcontext)).build();
		connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		// 消息约束
		MessageConstraints messageConstraints = MessageConstraints.custom().setMaxHeaderCount(HttpClientConstant.MAX_HEADER_COUNT).setMaxLineLength(HttpClientConstant.MAX_LINE_LENGTH).build();
		// 连接设置
		ConnectionConfig connectionConfig = ConnectionConfig.custom().setMalformedInputAction(CodingErrorAction.IGNORE).setUnmappableInputAction(CodingErrorAction.IGNORE).setCharset(DEFAULT_CHARSET).setMessageConstraints(messageConstraints).build();
		connManager.setDefaultConnectionConfig(connectionConfig);
		connManager.setMaxTotal(HttpClientConstant.MAX_TOTAL_CONNECTION);
		connManager.setDefaultMaxPerRoute(HttpClientConstant.MAX_PER_ROUTE_CONNECTION);
		HttpClients.custom().setConnectionManager(connManager);
		return connManager;
	}


	/**
	 * 获取SSL套接字对象 重点重点：设置tls协议的版本
	 *
	 * @return
	 */
	private static SSLContext createIgnoreVerifySSL() {
		SSLContext sslContext = null;// 创建套接字对象
		try {
			sslContext = SSLContext.getInstance(HttpClientConstant.SSL_VERSION);// 指定TLS版本
		} catch (NoSuchAlgorithmException e) {
			output("创建套接字失败！", e);
		}
		// 实现X509TrustManager接口，用于绕过验证
		X509TrustManager trustManager = new X509TrustManager() {
			@Override
			public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
			                               String paramString) throws CertificateException {
			}

			@Override
			public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
			                               String paramString) throws CertificateException {
			}

			@Override
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}
		};
		try {
			sslContext.init(null, new TrustManager[]{trustManager}, null);// 初始化sslContext对象
		} catch (KeyManagementException e) {
			output("初始化套接字失败！", e);
		}
		return sslContext;
	}

	/**
	 * 获取重试控制器
	 *
	 * @return
	 */
	private static HttpRequestRetryHandler getHttpRequestRetryHandler() {
		return new HttpRequestRetryHandler() {
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
				output(exception);
				if (executionCount > HttpClientConstant.TRY_TIMES) return false;
				if (exception instanceof NoHttpResponseException) {
					output("没有响应异常");
					sleep(1);
					return true;
                } else if (exception instanceof ConnectTimeoutException) {
                    output("连接超时，重试");
                    sleep(5);
                    return true;
				} else if (exception instanceof SSLHandshakeException) {
					output("本地证书异常");
					return false;
				} else if (exception instanceof InterruptedIOException) {
					output("IO中断异常");
					sleep(1);
					return true;
				} else if (exception instanceof UnknownHostException) {
					output("找不到服务器异常");
					return false;
				} else if (exception instanceof SSLException) {
					output("SSL异常");
					return false;
				} else if (exception instanceof HttpHostConnectException) {
					output("主机连接异常");
					return false;
				} else if (exception instanceof SocketException) {
					output("socket异常");
					return false;
				} else {
					output("未记录的请求异常：" + exception.getClass());
				}
				HttpClientContext clientContext = HttpClientContext.adapt(context);
				HttpRequest request = clientContext.getRequest();
				// 如果请求是幂等的，则重试
				if (!(request instanceof HttpEntityEnclosingRequest)) {
					sleep(2);
					return true;
				}
				return false;
			}
		};
	}

	/**
	 * 通过连接池获取https协议请求对象
	 *
	 * @return
	 */
	private static CloseableHttpClient getCloseableHttpsClients() {
		// 创建自定义的httpsclient对象
		CloseableHttpClient client = HttpClients.custom().setConnectionManager(connManager).setRetryHandler(httpRequestRetryHandler).setDefaultCookieStore(cookieStore).build();
		// CloseableHttpClient client = HttpClients.createDefault();//非连接池创建
		return client;
	}

	/**
	 * 回收资源方法，关闭过期连接，关闭超时连接，用于另起线程回收连接池连接
	 */
	public static void recyclingConnection() {
		connManager.closeExpiredConnections();
		connManager.closeIdleConnections(HttpClientConstant.IDLE_TIMEOUT, TimeUnit.MILLISECONDS);
	}
}
